﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
using ShortcutToolbox.Core;
using ShortcutToolbox.Properties;
using ShortcutToolbox.Controller;
using ShortcutToolbox.Model;

namespace ShortcutToolbox.View
{
    class MenuViewController
    {
        private MainController controller;

        private NotifyIcon _notifyIcon;
        private ContextMenu contextMenu1;
        private MenuItem _StartOnBootMenuItem;
        private MenuItem _CheckUpdateOnStartMenuItem;
        private MenuItem _ChooseLanguageMenuItem;
        private MenuItem _SeperatorMenuItem;
        private LinkedList<BalloonTipAction> _balloonTipActions;

        public MenuViewController(MainController controller)
        {
            _balloonTipActions = new LinkedList<BalloonTipAction>();

            this.controller = controller;
            controller.OnStart += onStart;
            controller.OnStop += onStop;
            controller.OnLanguageChanged += onLanguageChanged;
            controller.OnAutoStartupChanged += onAutoStartChanged;
            controller.OnCheckUpdateOnStartChanged += onCheckUpdateOnStartChanged;
            controller.OnShowAboutForm += onShowAboutForm;
            controller.OnShowConfigurationForm += onShowConfigurationForm;
            controller.OnShowLogForm += onShowLogForm;
            controller.OnCheckUpdate += onCheckUpdate;
        }

        private void CreateTrayIcon()
        {
            Bitmap bitmap = Resources.logo;
            Bitmap iconCopy = new Bitmap(bitmap);

            _notifyIcon = new NotifyIcon();
            _notifyIcon.DoubleClick += OnNotifyIconDoubleClick;
            _notifyIcon.BalloonTipClicked += OnBalloonTipClicked;
            _notifyIcon.Icon = Icon.FromHandle(iconCopy.GetHicon());
            _notifyIcon.Text = $"{I18N.Current.AppName} {VERSION.CurrentVersion}";
            _notifyIcon.Visible = true;
            _notifyIcon.ContextMenu = contextMenu1;

            iconCopy.Dispose();
        }

        private void ShowBalloonTip(string title, string content, ToolTipIcon icon, int timeout)
        {
            _notifyIcon.BalloonTipTitle = title;
            _notifyIcon.BalloonTipText = content;
            _notifyIcon.BalloonTipIcon = icon;
            _notifyIcon.ShowBalloonTip(timeout);
        }

        private void ShowFirstTimeBalloon()
        {
            System.Timers.Timer timer = new System.Timers.Timer(1000);
            timer.Elapsed += new System.Timers.ElapsedEventHandler((object sender, System.Timers.ElapsedEventArgs e) => {
                timer.Enabled = false;
                timer.Dispose();
                timer = null;
                ShowBalloonTip(I18N.Current.FirstTimeBalloonTitle,
                    I18N.Current.FirstTimeBalloonText,
                    ToolTipIcon.Info, 0);
            });
            timer.AutoReset = false;
            timer.Enabled = true;
        }

        private void OnNotifyIconDoubleClick(object sender, EventArgs e)
        {
            controller.ShowConfigurationForm(this);
        }

        private void OnBalloonTipClicked(object sender, EventArgs e)
        {
            while (_balloonTipActions.Count > 0)
            {
                BalloonTipAction action = _balloonTipActions.First.Value;
                _balloonTipActions.RemoveFirst();
                action.Execute(sender, e);
                action.Dispose();
            }
        }

        private MenuItem CreateMenuItem(string key, EventHandler click)
        {
            return new MyMenuItem(key, click);
        }

        private MenuItem CreateMenuGroup(string key, MenuItem[] items)
        {
            return new MyMenuItem(key, items);
        }

        private void CreateContextMenu()
        {
            contextMenu1 = new ContextMenu(new MenuItem[] {
                _SeperatorMenuItem = new MenuItem("-"),
                CreateMenuGroup("Options.Options", new MenuItem[] {
                    _StartOnBootMenuItem = CreateMenuItem("Options.StartOnBoot", new EventHandler(OnStartOnBootMenuItemClick)),
                    new MenuItem("-"),
                    CreateMenuItem("Options.Settings", new EventHandler(OnSettingsMenuItemClick)),
                }),
                _ChooseLanguageMenuItem = CreateMenuGroup("Help.ChooseLanguage", new MenuItem[] {
                }),
                CreateMenuGroup("Help.Help", new MenuItem[] {
                    CreateMenuItem("Help.Logs", new EventHandler(OnLogsMenuItemClick)),
                    CreateMenuGroup("Help.Updating", new MenuItem[] {
                        CreateMenuItem("Help.CheckUpdate", new EventHandler(OnCheckUpdateMenuItemClick)),
                        new MenuItem("-"),
                        _CheckUpdateOnStartMenuItem = CreateMenuItem("Help.CheckUpdateOnStart", new EventHandler(OnCheckUpdateOnStartMenuItemClick)),
                    }),
                    CreateMenuItem("Help.About", new EventHandler(OnAboutMenuItemClick)),
                }),
                new MenuItem("-"),
                CreateMenuItem("Quit", new EventHandler(OnQuitMenuItemClick))
            });
        }

        private void UpdatePluginMenu()
        {
            var items = contextMenu1.MenuItems;
            while (items[0] != _SeperatorMenuItem)
            {
                items.RemoveAt(0);
            }

            Dictionary<string, MenuItem> catalogDic = new Dictionary<string, MenuItem>();

            foreach (IPlugin plugin in controller.Plugins)
            {
                try
                {
                    MenuItem catalogMenu;
                    if (!catalogDic.ContainsKey(plugin.Catalog))
                    {
                        catalogMenu = new CatalogMenuItem(plugin.Catalog);
                        catalogDic.Add(plugin.Catalog, catalogMenu);
                    }
                    else
                        catalogMenu = catalogDic[plugin.Catalog];
                    foreach (IPluginMenu pm in plugin.Menus)
                    {
                        catalogMenu.MenuItems.Add(new PluginMenuItem(pm));
                    }
                }
                catch(Exception ex)
                {
                    Logging.LogUsefulException(ex);
                }
            }
            List<string> catalogs = new List<string>(catalogDic.Keys);
            catalogs.Sort(new PluginCatalogs.Comparer());
            List<MenuItem> menuItems = new List<MenuItem>();
            for (int i = 0; i < catalogs.Count; i++)
            {
                string catalog = catalogs[i];
                if(string.IsNullOrEmpty(catalog))
                {
                    foreach(MenuItem mi in catalogDic[catalog].MenuItems)
                    {
                        contextMenu1.MenuItems.Add(i, mi);
                    }
                }
                else
                {
                    contextMenu1.MenuItems.Add(i, catalogDic[catalog]);
                }
            }
        }

        private void UpdateLanguageMenu()
        {
            _ChooseLanguageMenuItem.MenuItems.Clear();
            foreach (dynamic lang in I18N.GetLangList())
            {
                MenuItem menuItem = new MenuItem(lang.alias, new EventHandler(OnLanguageMenuItemClick));
                menuItem.Tag = lang.name;
                _ChooseLanguageMenuItem.MenuItems.Add(menuItem);
                if (lang == I18N.Current)
                    menuItem.Checked = true;
            }
        }

        private void UpdateContextMenuLanguage(Menu.MenuItemCollection menuItems)
        {
            foreach (MenuItem item in menuItems)
            {
                if (item is ILangConnector)
                    (item as ILangConnector).OnLangeChanged(this, new LanguageChangedEventArgs(I18N.Current));
                UpdateContextMenuLanguage(item.MenuItems);
            }
        }

        private void onStart(object sender, EventArgs e)
        {
            CreateContextMenu();
            CreateTrayIcon();
            UpdateLanguageMenu();
            UpdatePluginMenu();
            _StartOnBootMenuItem.Checked = AutoStartup.IsSet();
            _CheckUpdateOnStartMenuItem.Checked = Configuration.Current.CheckUpdateOnStart;

            if (Configuration.Current.IsDefault)
            {
                ShowFirstTimeBalloon();
                controller.ShowConfigurationForm(this);
            }

            if (Configuration.Current.CheckUpdateOnStart)
            {
                controller.CheckUpdate(new CheckUpdateState { Delay = 1000 });
            }
        }

        private void onStop(object sender, EventArgs e)
        {
            _notifyIcon.Visible = false;
            Application.Exit();
        }

        private void onLanguageChanged(object sender, EventArgs e)
        {
            foreach (MenuItem menuItem in _ChooseLanguageMenuItem.MenuItems)
            {
                menuItem.Checked = (Convert.ToString(menuItem.Tag) == I18N.Current.name);
            }
            _notifyIcon.Text = $"{I18N.Current.AppName} {VERSION.CurrentVersion}";
            UpdatePluginMenu();
            UpdateContextMenuLanguage(contextMenu1.MenuItems);
        }

        private void onAutoStartChanged(object sender, BooleanValueChangedEventArgs e)
        {
            _StartOnBootMenuItem.Checked = e.Value;
        }

        private void onCheckUpdateOnStartChanged(object sender, BooleanValueChangedEventArgs e)
        {
            _CheckUpdateOnStartMenuItem.Checked = e.Value;
        }

        private void onShowAboutForm(object sender, ToolBoxEventArgs e)
        {
            if(e.UserState != null && (e.UserState is MenuViewController) && e.UserState == this)
            {
                AboutForm.Instance.Show();
                AboutForm.Instance.Activate();
            }
        }

        private void onShowLogForm(object sender, ToolBoxEventArgs e)
        {
            if (e.UserState != null && (e.UserState is MenuViewController) && e.UserState == this)
            {
                LogForm.Instance.Show();
                LogForm.Instance.Activate();
            }
        }

        private void onCheckUpdate(object sender, ToolBoxEventArgs e)
        {
            if (e.UserState != null && (((e.UserState is MenuViewController) && e.UserState == this) || (e.UserState is CheckUpdateState)))
            {
                int delay = 0;
                if (e.UserState is CheckUpdateState)
                    delay = (e.UserState as CheckUpdateState).Delay;
                UpdateChecker checker = new UpdateChecker
                {
                    CurrentVersion = VERSION.CurrentVersion,
                    ReleasePage = VERSION.ReleasePage,
                    ApiUrl = VERSION.ApiUrl,
                };
                checker.CheckUpdateCompleted += onCheckUpdateCompleted;
                checker.CheckUpdate(delay);
                //ShowBalloonTip("", I18N.Current.CheckingUpdate, ToolTipIcon.Info, 3000);
            }
        }

        private void onCheckUpdateCompleted(object sender, CheckUpdateEventArgs e)
        {
            try
            {
                if (e.ReleaseList.Count > 0)
                {
                    ShowBalloonTip("",
                        I18N.Current.NewVersionFound.ToString()
                            .Replace("{$app_name}", I18N.Current.AppName)
                            .Replace("{$version}", e.ReleaseList[0].version),
                        ToolTipIcon.Info, 3000);
                    BalloonTopOpenWebPageAction action = new BalloonTopOpenWebPageAction(VERSION.ReleasePage, 3000);
                    action.Timeout += OnBalloonTipActionTimeout;
                    action.Tag = _balloonTipActions.AddLast(action);
                }
                else
                {
                    ShowBalloonTip("",
                        I18N.Current.UpToDate.ToString().Replace("{$app_name}", I18N.Current.AppName),
                        ToolTipIcon.Info, 3000);
                }
            }
            catch (Exception ex)
            {
                Logging.LogUsefulException(ex);
            }
        }

        private void OnBalloonTipActionTimeout(object sender, EventArgs e)
        {
            BalloonTipAction action = (BalloonTipAction)sender;
            try
            {
                Logging.Debug("balloon tip action timeout");
                if (action.Tag is LinkedListNode<BalloonTipAction>)
                {
                    LinkedListNode<BalloonTipAction> node = action.Tag as LinkedListNode<BalloonTipAction>;
                    _balloonTipActions.Remove(node);
                }
            }
            catch (Exception ex)
            {
                Logging.LogUsefulException(ex);
            }
            finally
            {
                action.Dispose();
            }
        }

        private void onShowConfigurationForm(object sender, ToolBoxEventArgs e)
        {
            if (e.UserState != null && (e.UserState is MenuViewController) && e.UserState == this)
            {
                ConfigurationForm.Instance.Show();
                ConfigurationForm.Instance.Activate();
            }
        }

        private void OnLanguageMenuItemClick(object sender, EventArgs e)
        {
            MenuItem item = (MenuItem)sender;
            string langName = Convert.ToString(item.Tag);
            controller.SetLanguage(langName);
        }

        private void OnStartOnBootMenuItemClick(object sender, EventArgs e)
        {
            controller.SetAutoStart(!_StartOnBootMenuItem.Checked);
        }

        private void OnSettingsMenuItemClick(object sender, EventArgs e)
        {
            controller.ShowConfigurationForm(this);
        }

        private void OnCheckUpdateMenuItemClick(object sender, EventArgs e)
        {
            controller.CheckUpdate(this);
        }

        private void OnCheckUpdateOnStartMenuItemClick(object sender, EventArgs e)
        {
            controller.SetCheckUpdateOnStart(!_CheckUpdateOnStartMenuItem.Checked);
        }

        private void OnAboutMenuItemClick(object sender, EventArgs e)
        {
            controller.ShowAboutForm(this);
        }

        private void OnLogsMenuItemClick(object sender, EventArgs e)
        {
            controller.ShowLogForm(this);
        }

        private void OnQuitMenuItemClick(object sender, EventArgs e)
        {
            controller.Stop();
        }

        class MyMenuItem : MenuItem, ILangConnector
        {
            public string Key { get; set; }

            public MyMenuItem(string key)
                : base(string.Empty)
            {
                Key = key;
                OnLangeChanged(this, new LanguageChangedEventArgs(I18N.Current));
            }

            public MyMenuItem(string key, EventHandler onClick)
                : base(string.Empty, onClick)
            {
                Key = key;
                OnLangeChanged(this, new LanguageChangedEventArgs(I18N.Current));
            }

            public MyMenuItem(string key, MenuItem[] items)
                : base(string.Empty, items)
            {
                Key = key;
                OnLangeChanged(this, new LanguageChangedEventArgs(I18N.Current));
            }

            public virtual void OnLangeChanged(object sender, LanguageChangedEventArgs e)
            {
                if (Key == "-")
                {
                    Text = Key;
                    return;
                }
                try
                {
                    dynamic dylang = e.Value;
                    string text = string.Empty;
                    if (!string.IsNullOrEmpty(Key))
                        text = Convert.ToString(dylang[Key]);
                    if (string.IsNullOrEmpty(text))
                        text = Key;
                    Text = text;
                }
                catch (Exception ex)
                {
                    Logging.LogUsefulException(ex);
                }
            }
        }

        class CatalogMenuItem : MyMenuItem
        {
            public CatalogMenuItem(string key)
                : base(key)
            {
            }

            public CatalogMenuItem(string key, EventHandler onClick)
                : base(key, onClick)
            {
            }

            public CatalogMenuItem(string key, MenuItem[] items)
                : base(key, items)
            {
            }

            public override void OnLangeChanged(object sender, LanguageChangedEventArgs e)
            {
                if (Key == "-")
                {
                    Text = Key;
                    return;
                }
                try
                {
                    dynamic dylang = e.Value;
                    string text = string.Empty;
                    if (!string.IsNullOrEmpty(Key))
                        text = Convert.ToString(dylang[$"PluginCatalog.{Key}"]);
                    if (string.IsNullOrEmpty(text))
                        text = Key;
                    Text = text;
                }
                catch (Exception ex)
                {
                    Logging.LogUsefulException(ex);
                }
            }
        }

        class PluginMenuItem : MenuItem
        {
            private IPluginMenu _Info;

            public PluginMenuItem(IPluginMenu info)
                : base(info.Text)
            {
                _Info = info;
                OnStatusChanged(this, new EventArgs());
                info.StatusChanged += OnStatusChanged;
            }

            private void OnStatusChanged(object sender, EventArgs e)
            {
                try
                {
                    Text = _Info.Text;
                    Enabled = _Info.Enabled;
                    Visible = _Info.Visible;
                    Checked = _Info.Checked;
                    UpdateMenuItems(this, _Info.SubItems);
                }
                catch (Exception ex)
                {
                    Logging.LogUsefulException(ex);
                }
            }

            private void UpdateMenuItems(MenuItem menu, IList<IPluginMenu> items)
            {
                menu.MenuItems.Clear();
                foreach (IPluginMenu item in items)
                {
                    MenuItem mi = new PluginMenuItem(item);
                    menu.MenuItems.Add(mi);
                    if (item.SubItems.Count > 0)
                        UpdateMenuItems(mi, item.SubItems);
                }
            }

            protected override void OnClick(EventArgs e)
            {
                base.OnClick(e);
                try
                {
                    _Info.OnClick(e);
                }
                catch (Exception ex)
                {
                    Logging.LogUsefulException(ex);
                }
            }

            protected override void OnPopup(EventArgs e)
            {
                base.OnPopup(e);
                try
                {
                    _Info.OnPopup(e);
                }
                catch (Exception ex)
                {
                    Logging.LogUsefulException(ex);
                }
            }

            protected override void OnSelect(EventArgs e)
            {
                base.OnSelect(e);
                try
                {
                    _Info.OnSelect(e);
                }
                catch (Exception ex)
                {
                    Logging.LogUsefulException(ex);
                }
            }
        }

        abstract class BalloonTipAction : IDisposable
        {
            private System.Timers.Timer _timer;

            public event EventHandler Timeout;

            public object Tag { get; set; }

            public BalloonTipAction()
            { }

            public BalloonTipAction(int timeout)
            {
                _timer = new System.Timers.Timer(timeout);
                _timer.Elapsed += _timer_Elapsed;
                _timer.AutoReset = false;
                _timer.Enabled = true;
            }

            private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
            {
                disposeTimer();
                Timeout?.Invoke(this, new EventArgs());
            }

            private void disposeTimer()
            {
                try
                {
                    if (_timer != null)
                    {
                        _timer.Enabled = false;
                        _timer.Dispose();
                        _timer = null;
                    }
                }
                catch (Exception ex)
                {
                    Logging.LogUsefulException(ex);
                }
            }

            public void Execute(object sender, EventArgs e)
            {
                disposeTimer();
                OnExecute(sender, e);
            }

            protected abstract void OnExecute(object sender, EventArgs e);

            public void Dispose()
            {
                disposeTimer();
                Timeout = null;
            }
        }

        class BalloonTopOpenWebPageAction : BalloonTipAction
        {
            private string _url;

            public BalloonTopOpenWebPageAction(string url, int timeout)
                : base(timeout)
            {
                _url = url;
            }

            protected override void OnExecute(object sender, EventArgs e)
            {
                try
                {
                    Process.Start(_url);
                }
                catch (Exception ex)
                {
                    Logging.LogUsefulException(ex);
                }
            }
        }

        class CheckUpdateState
        {
            public int Delay { get; set; }
        }
    }
}
